package com.jcn.seckill.controller;

import java.awt.image.BufferedImage;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.jcn.seckill.access.AccessLimit;
import com.jcn.seckill.domain.MiaoshaOrder;
import com.jcn.seckill.domain.MiaoshaUser;
import com.jcn.seckill.rabbitmq.MQSender;
import com.jcn.seckill.rabbitmq.MiaoshaMessage;
import com.jcn.seckill.redis.GoodsKey;
import com.jcn.seckill.redis.MiaoshaKey;
import com.jcn.seckill.redis.OrderKey;
import com.jcn.seckill.redis.RedisService;
import com.jcn.seckill.result.CodeMsg;
import com.jcn.seckill.result.Result;
import com.jcn.seckill.service.GoodsService;
import com.jcn.seckill.service.MiaoshaService;
import com.jcn.seckill.service.MiaoshaUserService;
import com.jcn.seckill.service.OrderService;
import com.jcn.seckill.vo.GoodsVo;

//定义一个 Controller 控制器
@Controller
//映射请求,指定控制器可以处理哪些URL请求,相当于Servlet中在web.xml中配置
@RequestMapping("/miaosha")
public class MiaoshaController implements InitializingBean {

	//对类成员变量、方法及构造函数进行标注，完成自动装配的工作
	//@Autowired标注可以放在成员变量上，也可以放在成员变量的set方法上，也可以放在任意方法上表示
	@Autowired
	//注入服务  
	MiaoshaUserService userService;
	
	//对类成员变量、方法及构造函数进行标注，完成自动装配的工作
	//@Autowired标注可以放在成员变量上，也可以放在成员变量的set方法上，也可以放在任意方法上表示
	@Autowired
	//注入服务  
	RedisService redisService;
	
	//对类成员变量、方法及构造函数进行标注，完成自动装配的工作
	//@Autowired标注可以放在成员变量上，也可以放在成员变量的set方法上，也可以放在任意方法上表示
	@Autowired
	//注入服务  
	GoodsService goodsService;
	
	//对类成员变量、方法及构造函数进行标注，完成自动装配的工作
	//@Autowired标注可以放在成员变量上，也可以放在成员变量的set方法上，也可以放在任意方法上表示
	@Autowired
	//注入服务  
	OrderService orderService;
	
	//对类成员变量、方法及构造函数进行标注，完成自动装配的工作
	//@Autowired标注可以放在成员变量上，也可以放在成员变量的set方法上，也可以放在任意方法上表示
	@Autowired
	//注入服务  
	MiaoshaService miaoshaService;
	
	//对类成员变量、方法及构造函数进行标注，完成自动装配的工作
	//@Autowired标注可以放在成员变量上，也可以放在成员变量的set方法上，也可以放在任意方法上表示
	@Autowired
	//注入服务  
	MQSender sender;
	
	private HashMap<Long, Boolean> localOverMap =  new HashMap<Long, Boolean>();
	
	/**
	 * 系统初始化
	 * 回调接口
	 * */
	public void afterPropertiesSet() throws Exception {
		List<GoodsVo> goodsList = goodsService.listGoodsVo();
		if(goodsList == null) {
			return;
		}
		//商品库存加载到redis
		for(GoodsVo goods : goodsList) {
			redisService.set(GoodsKey.getMiaoshaGoodsStock, ""+goods.getId(), goods.getStockCount());
			//秒杀标识为未结束
			localOverMap.put(goods.getId(), false);
		}
	}
	//映射请求,指定控制器可以处理哪些URL请求,相当于Servlet中在web.xml中配置
	@RequestMapping(value="/reset", method=RequestMethod.GET)
	/**
     * 返回结果直接写入 HTTP response body 中，
     * 一般在异步获取数据时使用【也就是AJAX】，
     * 在使用 @RequestMapping后，返回值通常解析为跳转路径，
     * 但是加上 @ResponseBody 后返回结果不会被解析为跳转路径，
     * 而是直接写入 HTTP response body 中。 比如异步获取 json 数据，
     * 加上 @ResponseBody 后，会直接返回 json 数据。@RequestBody 
     * 将 HTTP 请求正文插入方法中，
     * 使用适合的 HttpMessageConverter 将请求体写入某个对象。
     * */
    @ResponseBody
    public Result<Boolean> reset(Model model) {
		List<GoodsVo> goodsList = goodsService.listGoodsVo();
		for(GoodsVo goods : goodsList) {
			goods.setStockCount(10);
			redisService.set(GoodsKey.getMiaoshaGoodsStock, ""+goods.getId(), 10);
			localOverMap.put(goods.getId(), false);
		}
		redisService.delete(OrderKey.getMiaoshaOrderByUidGid);
		redisService.delete(MiaoshaKey.isGoodsOver);
		miaoshaService.reset(goodsList);
		return Result.success(true);
	}
	
	/**
	 * QPS:1306
	 * 5000 * 10
	 * QPS: 2114
	 * */
	//映射请求,指定控制器可以处理哪些URL请求,相当于Servlet中在web.xml中配置
    @RequestMapping(value="/{path}/do_miaosha", method=RequestMethod.POST)
    /**
     * 返回结果直接写入 HTTP response body 中，
     * 一般在异步获取数据时使用【也就是AJAX】，
     * 在使用 @RequestMapping后，返回值通常解析为跳转路径，
     * 但是加上 @ResponseBody 后返回结果不会被解析为跳转路径，
     * 而是直接写入 HTTP response body 中。 比如异步获取 json 数据，
     * 加上 @ResponseBody 后，会直接返回 json 数据。@RequestBody 
     * 将 HTTP 请求正文插入方法中，
     * 使用适合的 HttpMessageConverter 将请求体写入某个对象。
     * */
    @ResponseBody
    //秒杀静态化
    /**思路：减少数据库访问
    1.系统初始化，把商品库存数量加载到redis
    2.收到请求，redis预减库存，库存不足，直接返回，否则进入3
    3.请求入队，立即返回排队中
    4.请求出队，生成订单，减少库存
    5.客户端轮询，是否秒杀成功
    */
    public Result<Integer> miaosha(Model model,MiaoshaUser user,
    		@RequestParam("goodsId")long goodsId,
    		@PathVariable("path") String path) {
    	model.addAttribute("user", user);
    	if(user == null) {
    		return Result.error(CodeMsg.SESSION_ERROR);
    	}
    	///////////////////////////////////////////////////////////////////////////////
    	//商品是有固定值的，当收到的请求大于商品存储值时，stock就会变为-2，-3，-4等等下去，也就是后面的操作始终为失败//
    	//失败的话，后面就没必要继续访问redis数据库的，以减少网络开销。 //////////////////////////////////
    	///////////////////////////////////////////////////////////////////////////////
    	//验证path
    	boolean check = miaoshaService.checkPath(user, goodsId, path);
    	if(!check){
    		return Result.error(CodeMsg.REQUEST_ILLEGAL);
    	}
    	//内存标记，减少redis访问
    	boolean over = localOverMap.get(goodsId);
    	if(over) {
    		return Result.error(CodeMsg.MIAO_SHA_OVER);
    	}
    	//预减库存
    	long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock, ""+goodsId);//10
    	if(stock < 0) {
    		//将标识改为true，标识秒杀结束
    		localOverMap.put(goodsId, true);
    		return Result.error(CodeMsg.MIAO_SHA_OVER);
    	}
    	//判断是否已经秒杀到了，查了数据库
    	MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
    	if(order != null) {//秒杀失败
    		return Result.error(CodeMsg.REPEATE_MIAOSHA);
    	}
    	//入队
    	//如果入队成功，客户端进行轮询
    	//入队成功，MQReceiver就能进行接收
    	MiaoshaMessage mm = new MiaoshaMessage();
    	//拼数据
    	mm.setUser(user);
    	mm.setGoodsId(goodsId);
    	sender.sendMiaoshaMessage(mm);
    	//消息队列出队成功后才访问数据库
    	return Result.success(0);//0：排队中
    	
    	/*
    	//判断库存，解决买超
    	GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);//10个商品，req1 req2
    	int stock = goods.getStockCount();
    	if(stock <= 0) {
    		return Result.error(CodeMsg.MIAO_SHA_OVER);
    	}
    	//判断是否已经秒杀到了
    	MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
    	if(order != null) {
    		return Result.error(CodeMsg.REPEATE_MIAOSHA);
    	}
    	//减库存 下订单 写入秒杀订单    ，查了数据库
    	OrderInfo orderInfo = miaoshaService.miaosha(user, goods);
    	//秒杀成功进入订单详情页
        return Result.success(orderInfo);
        */
    }
    
    /**
     * orderId：成功
     * -1：秒杀失败
     * 0： 排队中
     * */
    //映射请求,指定控制器可以处理哪些URL请求,相当于Servlet中在web.xml中配置
    @RequestMapping(value="/result", method=RequestMethod.GET)
    /**
     * 返回结果直接写入 HTTP response body 中，
     * 一般在异步获取数据时使用【也就是AJAX】，
     * 在使用 @RequestMapping后，返回值通常解析为跳转路径，
     * 但是加上 @ResponseBody 后返回结果不会被解析为跳转路径，
     * 而是直接写入 HTTP response body 中。 比如异步获取 json 数据，
     * 加上 @ResponseBody 后，会直接返回 json 数据。@RequestBody 
     * 将 HTTP 请求正文插入方法中，
     * 使用适合的 HttpMessageConverter 将请求体写入某个对象。
     * */
    @ResponseBody
    public Result<Long> miaoshaResult(Model model,MiaoshaUser user,
    		@RequestParam("goodsId")long goodsId) {
    	model.addAttribute("user", user);
    	if(user == null) {
    		return Result.error(CodeMsg.SESSION_ERROR);
    	}
    	long result  =miaoshaService.getMiaoshaResult(user.getId(), goodsId);
    	return Result.success(result);
    }
    
    //自定义注解，五秒钟最多访问5次
    @AccessLimit(seconds=5, maxCount=5, needLogin=true)
    //映射请求,指定控制器可以处理哪些URL请求,相当于Servlet中在web.xml中配置
    @RequestMapping(value="/path", method=RequestMethod.GET)
    /**
     * 返回结果直接写入 HTTP response body 中，
     * 一般在异步获取数据时使用【也就是AJAX】，
     * 在使用 @RequestMapping后，返回值通常解析为跳转路径，
     * 但是加上 @ResponseBody 后返回结果不会被解析为跳转路径，
     * 而是直接写入 HTTP response body 中。 比如异步获取 json 数据，
     * 加上 @ResponseBody 后，会直接返回 json 数据。@RequestBody 
     * 将 HTTP 请求正文插入方法中，
     * 使用适合的 HttpMessageConverter 将请求体写入某个对象。
     * */
    @ResponseBody
    public Result<String> getMiaoshaPath(HttpServletRequest request, MiaoshaUser user,
    		//GET和POST请求传的参数会自动转换赋值到@RequestParam 所注解的变量上
    		@RequestParam("goodsId")long goodsId,
    		//GET和POST请求传的参数会自动转换赋值到@RequestParam 所注解的变量上
    		@RequestParam(value="verifyCode", defaultValue="0")int verifyCode
    		) {
    	if(user == null) {
    		return Result.error(CodeMsg.SESSION_ERROR);
    	}
    	//验证验证码
    	boolean check = miaoshaService.checkVerifyCode(user, goodsId, verifyCode);
    	if(!check) {
    		return Result.error(CodeMsg.REQUEST_ILLEGAL);
    	}
    	String path  =miaoshaService.createMiaoshaPath(user, goodsId);
    	return Result.success(path);
    }
    
    //映射请求,指定控制器可以处理哪些URL请求,相当于Servlet中在web.xml中配置
    @RequestMapping(value="/verifyCode", method=RequestMethod.GET)
    /**
     * 返回结果直接写入 HTTP response body 中，
     * 一般在异步获取数据时使用【也就是AJAX】，
     * 在使用 @RequestMapping后，返回值通常解析为跳转路径，
     * 但是加上 @ResponseBody 后返回结果不会被解析为跳转路径，
     * 而是直接写入 HTTP response body 中。 比如异步获取 json 数据，
     * 加上 @ResponseBody 后，会直接返回 json 数据。@RequestBody 
     * 将 HTTP 请求正文插入方法中，
     * 使用适合的 HttpMessageConverter 将请求体写入某个对象。
     * */
    @ResponseBody
    public Result<String> getMiaoshaVerifyCod(HttpServletResponse response,MiaoshaUser user,
    		//GET和POST请求传的参数会自动转换赋值到@RequestParam 所注解的变量上
    		@RequestParam("goodsId")long goodsId) {
    	if(user == null) {
    		return Result.error(CodeMsg.SESSION_ERROR);
    	}
    	try {
    		BufferedImage image  = miaoshaService.createVerifyCode(user, goodsId);
    		OutputStream out = response.getOutputStream();
    		ImageIO.write(image, "JPEG", out);
    		out.flush();
    		out.close();
    		//OutputStream返回了，所以这里返回null
    		return null;
    	}catch(Exception e) {
    		e.printStackTrace();
    		return Result.error(CodeMsg.MIAOSHA_FAIL);
    	}
    }
}
